#include "screen.h"
#include "port_io.h"
#include "utils.h"

int get_cursor_offset();
void set_cursor_offset(int offset);
int print_char(char character, int col, int row, char attribute_byte);
int get_offset(int col, int row);
int get_offset_row(int offset);
int get_offset_col(int offset);
int scroll(int offset);

void clear_screen()
{
    int screen_size = MAX_COLS * MAX_ROWS;
    int i;
    char *screen = (char*)VIDEO_ADDRESS;

    for (i = 0; i < screen_size; i++) {
        screen[i*2] = ' ';
        screen[i*2+1] = WHITE_ON_BLACK;
    }
    set_cursor_offset(get_offset(0, 0));

    // char* screen = VIDEO_ADDRESS;
    // for(int i = 0; i < 2 * MAX_COLS * MAX_ROWS; i += 1)
    // {
    //     screen[i] = 0x50;
    //     i++;
    //     screen[i] = WHITE_ON_BLACK;
    // }
    // set_cursor_offset(get_offset(0, 0));
    
}

void kprint(char *message)
{
    kprint_at(message, -1, -1);
}

void kprint_at(char *message, int col, int row)
{
    int offset;
    if (col >= 0 && row >= 0 && col < MAX_COLS && row < MAX_ROWS)
    {
        offset = get_offset(col, row);
    }
    else
    {
        offset = get_cursor_offset();
        row = get_offset_row(offset);
        col = get_offset_col(offset);
    }

    int i = 0;
    while (message[i] != 0)
    {
        offset = print_char(message[i++], col, row, WHITE_ON_BLACK);
        row = get_offset_row(offset);
        col = get_offset_col(offset);
    }
    return;
}

/**
 * @brief 在第row行，第col列打印character，属性为attribute_byte
 * @param character char 需要打印的数据
 * @param col int 列号，[0, MAX_COLS - 1]
 * @param row int 行号，[0, MAX_ROWS - 1]
 * @param attribute_byte 打印属性
 * @return int 字节打印之后的光标位置
*/
int print_char(char character, int col, int row,
               char attribute_byte)
{
    unsigned char *vidmem = (unsigned char*)VIDEO_ADDRESS;
    if (!attribute_byte)
    {
        attribute_byte = WHITE_ON_BLACK;
    }

    int offset;
    if (col >= MAX_COLS || row >= MAX_ROWS)
    {
        // 如果出错，则在右下角打印红色的E
        vidmem += 2 * (MAX_COLS)*MAX_ROWS - 2;
        *vidmem = 'E';
        vidmem++;
        *vidmem = RED_ON_WHITE;
        // vidmem[2 * (MAX_COLS)*MAX_ROWS - 2] = 'E';
        // vidmem[2 * MAX_COLS * MAX_ROWS - 1] = RED_ON_WHITE;
        return get_cursor_offset();
    }

    if (col >= 0 && row >= 0)
    {
        offset = get_offset(col, row);
    }
    else
    {
        offset = get_cursor_offset();
    }

    if (character == '\n')
    {
        row = get_offset_row(offset);
        offset = get_offset(0, row + 1);
    }
    else
    {
        vidmem += offset;
        *vidmem = character;
        vidmem++;
        *vidmem = attribute_byte;
        // vidmem[offset] = character;
        // vidmem[offset + 1] = attribute_byte;
        offset += 2;
    }

    offset = scroll(offset);

    set_cursor_offset(offset / 2);//因为每个字符显示占用两个字符
    return offset;
}

/**
 * @brief 滚屏。当打印到最后一行最后一个字符后，就进行滚屏,空出一行
 * @param offset int 当前字符下一个的位置
 * @return int offset 返回滚屏之后第一个空白字符的位置
*/
int scroll(int offset)
{
    if (offset < 2 * MAX_COLS * MAX_ROWS)
        return offset;
    char *src = (char*)VIDEO_ADDRESS;
    char *dest = src + MAX_COLS * 2;
    for (int i = 0; i < MAX_ROWS - 1; i++)
    {
        memorycopy(src, dest, MAX_COLS * 2);
        src = dest;
        dest = src + MAX_COLS * 2;
    }
    for (int i = 0; i < MAX_COLS; i++)
        dest[i] = 0;
    return get_offset(0, MAX_ROWS - 1);
}

/**
 * @brief 从VGA获取当前光标所处的位置
 * @return offset 偏移
*/
inline int get_cursor_offset()
{
    port_byte_out(REG_SCREEN_CTRL, 14);
    int offset = port_byte_in(REG_SCREEN_DATA) << 8; //高8bit
    port_byte_out(REG_SCREEN_CTRL, 15);
    offset += port_byte_in(REG_SCREEN_DATA);
    return offset * 2;
}

/**
 * @brief 设置光标在屏幕上的位置
 * @return void
*/
inline void set_cursor_offset(int offset)
{
    //首先设置高8bit
    port_byte_out(REG_SCREEN_CTRL, 14);
    port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset >> 8));
    //设置低8bit
    port_byte_out(REG_SCREEN_CTRL, 15);
    port_byte_out(REG_SCREEN_DATA, (unsigned char)(offset & 0xff));
    return;
}

/**
 * @brief 根据行列位置计算光标offset
 * @return int 位置offset
*/
inline int get_offset(int col, int row)
{
    return (row * MAX_COLS + col) * 2;
}

/**
 * @brief 根据位置计算光标所在行
 * @return int 行号
*/
inline int get_offset_row(int offset)
{
    return offset / (2 * MAX_COLS);
}

/**
 * @brief 根据位置计算光标所在列
 * @return int 列号
*/
inline int get_offset_col(int offset)
{
    return (offset - get_offset_row(offset) * MAX_COLS * 2) / 2;
}